Add a `cargo run` command
authorAlex Crichton <alex@alexcrichton.com>
Sun, 13 Jul 2014 18:47:37 +0000 (11:47 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Mon, 14 Jul 2014 13:40:51 +0000 (06:40 -0700)
This currently only supports executing the `src/main.rs` convention, no other.

Close #149

Makefile
src/bin/cargo-build.rs
src/bin/cargo-run.rs [new file with mode: 0644]
src/bin/cargo-test.rs
src/bin/cargo.rs
src/cargo/ops/cargo_compile.rs
src/cargo/ops/cargo_run.rs [new file with mode: 0644]
src/cargo/ops/mod.rs
tests/test_cargo_run.rs [new file with mode: 0644]
tests/tests.rs

index e10fedb1aafdf69c801f23db1390ec4f2a140f84..fa107cddf4bb09c81d85d5f74e6ec138e696a8bc 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -22,6 +22,7 @@ BINS = cargo \
             cargo-verify-project \
             cargo-git-checkout \
                 cargo-test \
+                cargo-run
 
 SRC = $(shell find src -name '*.rs' -not -path 'src/bin*')
 
index 9e4ef547e8804e818d9db16de617d4a214203616..81c5102123b80b72159f021a845b19c7576c61ce 100644 (file)
@@ -56,7 +56,7 @@ fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
         "compile"
     };
 
-    let opts = CompileOptions {
+    let mut opts = CompileOptions {
         update: options.update_remotes,
         env: env,
         shell: shell,
@@ -64,7 +64,7 @@ fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
         target: options.target.as_ref().map(|t| t.as_slice()),
     };
 
-    ops::compile(&root, opts).map(|_| None).map_err(|err| {
+    ops::compile(&root, &mut opts).map(|_| None).map_err(|err| {
         CliError::from_boxed(err, 101)
     })
 }
diff --git a/src/bin/cargo-run.rs b/src/bin/cargo-run.rs
new file mode 100644 (file)
index 0000000..216d783
--- /dev/null
@@ -0,0 +1,69 @@
+#![crate_name = "cargo-run"]
+#![feature(phase)]
+
+#[phase(plugin, link)]
+extern crate cargo;
+extern crate serialize;
+
+#[phase(plugin, link)]
+extern crate hammer;
+
+use std::os;
+use std::io::process::ExitStatus;
+
+use cargo::ops;
+use cargo::{execute_main_without_stdin};
+use cargo::core::{MultiShell};
+use cargo::util::{CliResult, CliError};
+use cargo::util::important_paths::find_project_manifest;
+
+#[deriving(PartialEq,Clone,Decodable)]
+struct Options {
+    manifest_path: Option<String>,
+    jobs: Option<uint>,
+    update: bool,
+    rest: Vec<String>,
+}
+
+hammer_config!(Options "Run the package's main executable", |c| {
+    c.short("jobs", 'j').short("update", 'u')
+})
+
+fn main() {
+    execute_main_without_stdin(execute);
+}
+
+fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
+    let root = match options.manifest_path {
+        Some(path) => Path::new(path),
+        None => try!(find_project_manifest(&os::getcwd(), "Cargo.toml")
+                    .map_err(|_| {
+                        CliError::new("Could not find Cargo.toml in this \
+                                       directory or any parent directory",
+                                      102)
+                    }))
+    };
+
+    let mut compile_opts = ops::CompileOptions {
+        update: options.update,
+        env: "compile",
+        shell: shell,
+        jobs: options.jobs,
+        target: None,
+    };
+
+    let err = try!(ops::run(&root, &mut compile_opts,
+                            options.rest.as_slice()).map_err(|err| {
+        CliError::from_boxed(err, 101)
+    }));
+    match err {
+        None => Ok(None),
+        Some(err) => {
+            Err(match err.exit {
+                Some(ExitStatus(i)) => CliError::from_boxed(box err, i as uint),
+                _ => CliError::from_boxed(box err, 101),
+            })
+        }
+    }
+}
+
index fdd9dce7222b0ce8bb80703c5732c8f4de3cfbb3..eb9dfeaf9c84479f19806c37208e68884f7d4fbc 100644 (file)
@@ -44,7 +44,7 @@ fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
                     }))
     };
 
-    let compile_opts = ops::CompileOptions {
+    let mut compile_opts = ops::CompileOptions {
         update: options.update,
         env: "test",
         shell: shell,
@@ -52,7 +52,8 @@ fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
         target: None,
     };
 
-    let test_executables = try!(ops::compile(&root, compile_opts).map_err(|err| {
+    let test_executables = try!(ops::compile(&root,
+                                             &mut compile_opts).map_err(|err| {
         CliError::from_boxed(err, 101)
     }));
 
index 8379a85eb710ce2480c767e70366829c172227de..2de801900ec59f38d538064071e291d6e339b793 100644 (file)
@@ -51,8 +51,10 @@ fn execute() {
             println!("Commands:");
             println!("  build          # compile the current project");
             println!("  test           # run the tests");
-            println!("  clean          # remove the target directory\n");
-            
+            println!("  clean          # remove the target directory");
+            println!("  run            # build and execute src/main.rs");
+            println!("");
+
 
             let (_, options) = hammer::usage::<GlobalFlags>(false);
             println!("Options (for all commands):\n\n{}", options);
index 9fdf73b2010a9c0c93285cc12728fd7b4e148ecd..18b0c0587c162857453fee772f31c9949e97763d 100644 (file)
@@ -38,8 +38,9 @@ pub struct CompileOptions<'a> {
     pub target: Option<&'a str>,
 }
 
-pub fn compile(manifest_path: &Path, options: CompileOptions) -> CargoResult<Vec<String>> {
-    let CompileOptions { update, env, shell, jobs, target } = options;
+pub fn compile(manifest_path: &Path,
+               options: &mut CompileOptions) -> CargoResult<Vec<String>> {
+    let CompileOptions { update, env, ref mut shell, jobs, target } = *options;
     let target = target.map(|s| s.to_string());
 
     log!(4, "compile; manifest-path={}", manifest_path.display());
@@ -60,7 +61,7 @@ pub fn compile(manifest_path: &Path, options: CompileOptions) -> CargoResult<Vec
     let source_ids = package.get_source_ids();
 
     let (packages, resolve) = {
-        let mut config = try!(Config::new(shell, update, jobs, target.clone()));
+        let mut config = try!(Config::new(*shell, update, jobs, target.clone()));
 
         let mut registry =
             try!(PackageRegistry::new(source_ids, override_ids, &mut config));
@@ -83,7 +84,7 @@ pub fn compile(manifest_path: &Path, options: CompileOptions) -> CargoResult<Vec
         target.get_profile().get_env() == env
     }).collect::<Vec<&Target>>();
 
-    let mut config = try!(Config::new(shell, update, jobs, target));
+    let mut config = try!(Config::new(*shell, update, jobs, target));
 
     try!(ops::compile_targets(env.as_slice(), targets.as_slice(), &package,
          &PackageSet::new(packages.as_slice()), &resolve, &mut config));
diff --git a/src/cargo/ops/cargo_run.rs b/src/cargo/ops/cargo_run.rs
new file mode 100644 (file)
index 0000000..6b6198a
--- /dev/null
@@ -0,0 +1,23 @@
+use std::os;
+
+use ops;
+use util::{CargoResult, human, process, ProcessError};
+
+pub fn run(manifest_path: &Path,
+           options: &mut ops::CompileOptions,
+           args: &[String]) -> CargoResult<Option<ProcessError>> {
+    if !manifest_path.dir_path().join("src").join("main.rs").exists() {
+        return Err(human("`src/main.rs` must be present for `cargo run`"))
+    }
+
+    try!(ops::compile(manifest_path, options));
+    let exe = manifest_path.dir_path().join("target/main");
+    let exe = match exe.path_relative_from(&os::getcwd()) {
+        Some(path) => path,
+        None => exe,
+    };
+    let process = process(exe).args(args);
+
+    try!(options.shell.status("Running", process.to_string()));
+    Ok(process.exec().err())
+}
index d47b136201325296e055bb4445a6601f18610339..cba0e894ad867ed0f984cdf6198b9fb19681ad34 100644 (file)
@@ -2,8 +2,10 @@ pub use self::cargo_clean::clean;
 pub use self::cargo_compile::{compile, CompileOptions};
 pub use self::cargo_read_manifest::{read_manifest,read_package,read_packages};
 pub use self::cargo_rustc::compile_targets;
+pub use self::cargo_run::run;
 
 mod cargo_clean;
 mod cargo_compile;
 mod cargo_read_manifest;
 mod cargo_rustc;
+mod cargo_run;
diff --git a/tests/test_cargo_run.rs b/tests/test_cargo_run.rs
new file mode 100644 (file)
index 0000000..355884e
--- /dev/null
@@ -0,0 +1,84 @@
+use std::path;
+
+use support::{project, execs};
+use support::{COMPILING, RUNNING};
+use hamcrest::{assert_that, existing_file};
+
+fn setup() {
+}
+
+test!(simple {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+        "#)
+        .file("src/main.rs", r#"
+            fn main() { println!("hello"); }
+        "#);
+
+    assert_that(p.cargo_process("cargo-run"),
+                execs().with_status(0).with_stdout(format!("\
+{compiling} foo v0.0.1 (file:{dir})
+{running} `target{sep}main`
+hello
+",
+        compiling = COMPILING,
+        running = RUNNING,
+        dir = p.root().display(),
+        sep = path::SEP).as_slice()));
+    assert_that(&p.bin("main"), existing_file());
+})
+
+test!(simple_with_args {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+        "#)
+        .file("src/main.rs", r#"
+            fn main() {
+                assert_eq!(std::os::args().get(1).as_slice(), "hello");
+                assert_eq!(std::os::args().get(2).as_slice(), "world");
+            }
+        "#);
+
+    assert_that(p.cargo_process("cargo-run").arg("hello").arg("world"),
+                execs().with_status(0));
+})
+
+test!(exit_code {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+        "#)
+        .file("src/main.rs", r#"
+            fn main() { std::os::set_exit_status(2); }
+        "#);
+
+    assert_that(p.cargo_process("cargo-run"),
+                execs().with_status(2));
+})
+
+test!(no_main_file {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+        "#)
+        .file("src/lib.rs", "");
+
+    assert_that(p.cargo_process("cargo-run"),
+                execs().with_status(101)
+                       .with_stderr("`src/main.rs` must be present for \
+                                     `cargo run`\n"));
+})
index 595287a18da3ed16e4b1cfae048bec52f503df17..7db578ec785e0c7ae5a2e3bac2b553ede47bbdcb 100644 (file)
@@ -27,3 +27,4 @@ mod test_cargo_compile_path_deps;
 mod test_cargo_test;
 mod test_shell;
 mod test_cargo_cross_compile;
+mod test_cargo_run;